package com.gitee.l0km.aocache;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * 简单日志输出工具类
 * @author guyadong
 *
 */
class SimpleLog implements AocacheConstant {
	/** 占位符 */
	private static final String DELIM_STR = "(\\{\\}|%s)";
	private final static OutputString outputString = createOutputString();
	private static OutputString createOutputString() {
		if(isAndroid()){
			return new AndroidOutput();
		}else {
			return new StandardOutput();
		}
	}
    public static boolean isAndroid() {
        String vmName = System.getProperty("java.vm.name");
		if (vmName == null) { // default is false
			return false;
		}
        String lowerVMName = vmName.toLowerCase();
        
        return lowerVMName.contains("dalvik") //
               || lowerVMName.contains("lemur") // aliyun-vm name
        ;
    }
	/**
	 * 返回格式化的日志信息字符串<br>
	 * example:
	 * <pre>
	 * logString("name : {},age:{}","tom",23);
	 * </pre>
	 * @param format 格式字符串,采用"{}"或"%s"为占位符
	 * @param args 填充占位符的参数列表,如果数量小于占位符个数则多出的占位符填充"null"
	 */
	public static String logString(String format, Object ... args){
		if(null == format){
			return "";
		}
		if(null == args){
			args = EMPTY_OBJECT_ARRAY;
		}
		StringBuilder buffer = new StringBuilder(format.length() + 64);
		int beginIndex = 0,count = 0;
		Pattern pattern = Pattern.compile(DELIM_STR);
		Matcher matcher = pattern.matcher(format);
		while(matcher.find()){
			buffer.append(format.substring(beginIndex,matcher.start()));
			try{
				buffer.append(args[count++]);
			}catch(IndexOutOfBoundsException e){
				// 数组越界时对应占位符填null
				buffer.append("null");
			}
			beginIndex = matcher.end();
		}
		buffer.append(format.substring(beginIndex,format.length()));
		return buffer.toString();
	}
	public static void log(PrintStream printStream,int level, String format, Object ... args){
		if(null == printStream){
			return;
		}
		Thread currentThread = Thread.currentThread();
		StackTraceElement stackTrace = currentThread.getStackTrace()[level];
		printStream.printf("[%s] (%s:%d) %s\n",
				currentThread.getName(),
				stackTrace.getFileName(),
				stackTrace.getLineNumber(),
				logString(format,args));
	}
	public static void log(OutputString outputString,Level level,String format, Object ... args){
		if(null == outputString){
			return;
		}
		Thread currentThread = Thread.currentThread();
		StackTraceElement stackTrace = currentThread.getStackTrace()[outputString.stackLeve()];
		outputString.log(level, String.format("[%s] (%s:%d) %s %s\n",
				currentThread.getName(),
				stackTrace.getFileName(),
				stackTrace.getLineNumber(),
				stackTrace.getMethodName(),
				logString(format,args)));
	}
	/**
	 * 向{@code printStream}输出日志信息<br>
	 * example:
	 * <pre>
	 * log("name : {},age:{}","tom",23);
	 * </pre>
	 * @param printStream
	 * @param format 格式字符串,采用"{}"或"%s"为占位符,占位符个数要与{@code args}数组长度匹配
	 * @param args 填充占位符的参数列表,如果数量小于占位符个数则多出的占位符填充"null"
	 */
	public static void log(PrintStream printStream,String format, Object ... args){
		log(printStream,3,format,args);	
	}
	/**
	 * @param output 是否输出
	 * @param printStream
	 * @param format 格式字符串
	 * @param args 填充占位符的参数列表
	 * @see #log(PrintStream, String, Object...)
	 */
	public static void log(boolean output,PrintStream printStream,String format, Object ... args){
		if(output){
			log(printStream,3,format,args);
		}
	}
	private static final String formatByCount(int c){
		StringBuffer buffer = new StringBuffer();
		for(int i=0;i<c;++i){
			if(i>0){
				buffer.append(",");
			}
			buffer.append("{}");
		}
		return buffer.toString();
	}
	/**
	 * 向控制台输出日志信息<br>
	 * @param arg
	 * @see #log(OutputString, Level, String, Object...)
	 */
	public static void logObjects(Object arg){
		log(outputString,Level.INFO,formatByCount(1),new Object[]{arg});
	}
	/**
	 * 向控制台输出日志信息<br>
	 * @param arg
	 * @param args
	 * @see #log(PrintStream, String, Object...)
	 */
	public static void logObjects(Object arg,Object ... args){
		Object[] array = new Object[args.length+1];
		array[0] = arg;
		System.arraycopy(args, 0, array, 1, args.length);
		log(outputString,Level.INFO,formatByCount(args.length),array);
	}
	/**
	 * 向控制台输出日志信息<br>
	 * @param format 格式字符串,采用"{}"或"%s"为占位符
	 * @param args
	 * @see #log(OutputString, Level, String, Object...)
	 */
	public static void log(String format, Object ... args){
		log(outputString,Level.INFO,format,args);
	}
	/**
	 * 向控制台输出日志信息<br>
	 * @param output 是否输出
	 * @param format 格式字符串,采用"{}"或"%s"为占位符
	 * @param args
	 * @see #log(OutputString, Level, String, Object...)
	 */
	public static void log(boolean output,String format, Object ... args){
		if(output){
			log(outputString,Level.INFO,format,args);
		}
	}
	
	public static void log(String msg,Throwable e){
		log(outputString,Level.ERROR,"{}\n{}",msg,stackTraceOf(e));
	}
	public static void log(Throwable e){
		log(outputString,Level.ERROR,"{}",stackTraceOf(e));
	}
	public static final String stackTraceOf(Throwable e){
		if(e == null){
			e = new NullPointerException("e is null");
		}
		
		StringWriter writer = new StringWriter();
		PrintWriter pw = new PrintWriter(writer);
		e.printStackTrace(pw);
		return writer.toString();
	}
	/**
	 * @author guyadong
	 * @see 2.8.0
	 */
	enum Level{
		INFO,ERROR
	}
	/**
	 * 消息输出接口<br>
	 * 应用层通过实现此接口实现日志输出
	 * @see StandardOutput
	 * @see AndroidOutput
	 * @author guyadong
	 * @see 2.8.0
	 */
	static interface OutputString{

		/**
		 * 日志信息输出
		 * @param level 日志输出类型
		 * @param message 待输出日志信息
		 */
		void log(Level level, String message);

		/**
		 * 返回输出行号类名所需要的堆栈级数
		 */
		int stackLeve();
	}
	/**
	 * 基于标准控制台输出的{@link OutputString}实现
	 * @author guyadong
	 * @see 2.8.0
	 */
	static class StandardOutput implements OutputString{
		final Map<Level, PrintStream>streams;
		public StandardOutput() {
			streams = new HashMap<>();
			streams.put(Level.INFO, System.out);
			streams.put(Level.ERROR, System.err);
		}

		@Override
		public void log(Level level, String message) {
			streams.get(level).print(String.valueOf(message));
		}
		@Override
		public int stackLeve() {
			return 3;
		}
	}
	/**
	 * android平台日志(android.util.Log)控制台输出的{@link OutputString}实现<br>
	 * 非android平台调用会抛出{@link RuntimeException}异常
	 * @author guyadong
	 * @see 2.8.0
	 */
	static class AndroidOutput implements OutputString{
		/** 默认日志标签 */
		public static final String DEFAULT_TAG = "SIMPLE_LOG";
		private final String defaultTag;
		private final Map<Level, Method>methods;
		/**
		 * 构造方法
		 * @param defaultTag 日志输出标签名,为{@code null}使用默认值{@link #DEFAULT_TAG}
		 */
		public AndroidOutput(String defaultTag){
			methods = new HashMap<>();
			this.defaultTag = (null == defaultTag) ? DEFAULT_TAG : defaultTag;
			methods.put(Level.INFO, methodOfLog("i"));
			methods.put(Level.ERROR, methodOfLog("e"));
		}
		/**
		 * 使用默认日志标签的构造方法
		 */
		public AndroidOutput() {
			this(null);
		}
		/** 反射方式获取 Log的指定方法 方法 */
		static Method methodOfLog(String name) {
			try {
				Class<?> logClass = Class.forName("android.util.Log");
				return logClass.getMethod(name, String.class,String.class);
			} catch (ClassNotFoundException e) {
				throw new UnsupportedOperationException("UNSUPPORTED PLATFORM,android reqiured");
			} catch (Exception e) {
				throw new RuntimeException(e);
			} 
		}
		@Override
		public void log(Level level, String message) {
			try {
				methods.get(level).invoke(null, defaultTag,String.valueOf(message));
			} catch (InvocationTargetException e) {
				throw new RuntimeException(e.getTargetException());
			}catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		@Override
		public int stackLeve() {
			return 4;
		}
	}
}
